package com.ywk.base.javaBase.generic;

/**
 * 泛型：类型参数化，从容器的角度去理解泛型，一个容器中可以存放各种类型的元素，“各种类型”就代表变量，所以就有了泛型，即类型参数化
 * 泛型有三种使用，泛型类、泛型接口、泛型方法
 * 现在定义一个容器，这个容器可以装特定某些类型的元素，这个类就可以定义位泛型类，那如何装特定的元素呢，这就需要引入通配符<?>，以此指定
 * 容器是用来装哪些元素的，这就有了上边界<? extends Number>和下边界<? super Number>
 *
 * 泛型中的T可以是任意标识，常见的如T、E、K、V等形式的参数常用于表示泛型，在实例化泛型类时，必须指定T的具体类型
 * 泛型有三种使用方式：
 * 1.泛型类：在实例化类的时候指明泛型的具体类型
 * 2.泛型接口：泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
 * 3.泛型方法：在调用方法的时候指明泛型的具体类型（标志：<T>，即 public 与返回值中间有<T>，此方法才为泛型方法。），
 * 所以下面的 setKey 和 getKey 方法不是泛型方法
 *
 * @Author zbh
 * @Date 2023/3/29
 */
public class Generic<T> {

    //key这个成员变量的类型为T,T的类型由外部指定
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T，T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T，T的类型由外部指定
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

    //===================泛型通配符=================
    public static void showKeyValue1(Generic<Number> obj){
        System.out.println("key_val = " + obj.getKey());
    }

    //泛型通配符
    public static void showKeyValue2(Generic<?> obj){
        System.out.println("key_val = " + obj.getKey());
    }

    //泛型上边界：?是 Number 的子类
    public static void showKeyValue3(Generic<? extends Number> obj){
        System.out.println("key_val = " + obj.getKey());
    }

    //泛型下边界：?是 Integer 的父类
    public static void showKeyValue4(Generic<? super Integer> obj){
        System.out.println("key_val = " + obj.getKey());
    }

    //===================泛型方法=================
    //泛型方法可以出现在任何地方和任何场景中使用,在泛型类中声明了一个泛型方法，使用泛型E，这种泛型E可以为任意类型。可以类型与T相同，也可以不同。
    //由于泛型方法在声明的时候会声明泛型<E>，因此即使在泛型类中并未声明泛型，编译器也能够正确识别泛型方法中识别的泛型。
    public static <E> E showKeyName(Generic<E> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适，只是为了说明泛型方法的特性。
        E test = container.getKey();
        return test;
    }

    //在泛型类中声明了一个泛型方法，使用泛型T，注意这个T是一种全新的类型，可以与泛型类中声明的T不是同一种类型。
    public <T> void show_2(T t){
        System.out.println(t.toString());
    }

    //泛型方法与可变参数

    /**
     *规范①：调用可变参数的方法时，传入实参的个数可以是0~n。（n = 0，1，2......）
     *规范②：传入的实参也可以直接是一个数组。
     *规范③：可变参数的本质，其实就是一个数组，因此你也可以把它当数组来用。
     *规范④：当形参中既含有可变参数，也含有普通的参数，必须确保可变参数在形参列表的最后。
     *规范⑤：每个形参列表都只能有一个可变参数。
     *
     * @param args
     * @param <T>
     */
    public static  <T> void printMsg( T... args){
        for(T t : args){
            System.out.println("泛型方法可变参数：" + t);
        }
    }


    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //泛型基本应用
        Generic<Integer> gInteger = new Generic<Integer>(123456);
        Generic<String> gString = new Generic<String>("key_val");
        Generic generic = new Generic(false);
        System.out.println("key_val = " + generic.getKey());
        generic.setKey("2");
        System.out.println("key_val = " + generic.getKey());

        //泛型的类型参数只能是类类型（包括自定义类,即引用类型），不能是简单类型
        //Generic<int> genericString = new Generic<int>(1); //编译不通过

        System.out.println("=========泛型通配符========");

        //泛型通配符应用
        Generic<Number> gNumber = new Generic<Number>(456);
        showKeyValue1(gNumber);
        //showKeyValue1(gInteger);//编译不通过，泛型通配符就是解决父子类关系的
        showKeyValue2(gInteger);
        showKeyValue3(gInteger);
        showKeyValue4(gNumber);

        System.out.println("=========泛型方法========");
        System.out.println(showKeyName(gNumber));
        printMsg("111",222,"aaaa","2323.4",55.55,gNumber);
    }

}
class Generic1{

    //泛型方法可以出现在任何地方和任何场景中使用
    public <E> E showKeyName(Generic<E> container){
        System.out.println("container key :" + container.getKey());
        E test = container.getKey();
        return test;
    }
}